package pl.skubicha.controller;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.apache.commons.lang.StringUtils;
import org.jboss.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.DigestUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import pl.skubicha.controller.property.editors.GroupsPropertyEditor;
import pl.skubicha.domain.Groups;
import pl.skubicha.domain.Roles;
import pl.skubicha.domain.Users;
import pl.skubicha.exceptions.EntityConstraintException;
import pl.skubicha.exceptions.UserNameAlreadyExistsException;
import pl.skubicha.exceptions.UserNotFoundException;
import pl.skubicha.response.GroupDTO;
import pl.skubicha.response.SearchDTO;
import pl.skubicha.response.UserDTO;
import pl.skubicha.service.GroupsService;
import pl.skubicha.service.RoleService;
import pl.skubicha.service.UserService;
import pl.skubicha.utils.UserMapper;
import pl.skubicha.utils.Utils;

@Controller
@RequestMapping( { "/users" } )
public class UsersController {

	private static final String TEACHERS = "teachers";
	private static final String TEACHER_ROLE_NAME = "Wykładowca";
	private static final String STUDENT_ROLE_NAME = "Student";
	private static final String USER_NAME = "login";
	private static final String FIRST_NAME = "firstName";
	private static final String LAST_NAME = "lastName";
	private static final String EMAIL = "email";
	private static final String ALBUM_NUMBER = "albumNumber";
	private static final String GROUP_NAME = "groups.groupName";
	private static final String STATUS = "status";

	private static final int USERS_PER_PAGE = 10; 
	private static final int STUDENTS_PERPAGE = 50;
	private static final int DEFAULT_PAGE = 1;

	private static Logger logger = Logger.getLogger( UsersController.class );

	@Autowired
	private UserService service;

	@Autowired
	private RoleService roleService;

	@Autowired
	private GroupsService groupService;

	private Integer idGroup;
	
	@ModelAttribute( "allGroups" )
	public List< Groups > getAllGroups() {
		return groupService.findAll();
	}
	
	@ModelAttribute( "allRoles" )
	public List< Roles > getAllRoles() {
		return roleService.findAll();
	}
	
	@InitBinder
	protected void initBinder( final HttpServletRequest request, final ServletRequestDataBinder binder ) {
		binder.registerCustomEditor( Groups.class, new GroupsPropertyEditor( this.groupService ) );
	}
	
	@RequestMapping( value = { "/profile" }, method = RequestMethod.GET )
	public String getOwnerProfile( final ModelMap model ) {
		try {
			final String loggedUserName = SecurityContextHolder.getContext().getAuthentication().getName();
			final Users user = service.findByLogin( loggedUserName );
			model.addAttribute( "user", UserMapper.map( user ) );
		} catch ( UserNotFoundException e ) {
			logger.debug( e );
			model.addAttribute( "status", "app.user.not.found" );
		}
		
		return "users/myProfile";
	}
	
	@RequestMapping( value = { "/{id}/profile" }, method = RequestMethod.GET )
	public String getProfile( @PathVariable( value = "id" ) final Integer id, final ModelMap model ) {
		try {
			Users user = service.findById( id );
			model.addAttribute( "user", UserMapper.map( user ) );
		} catch ( UserNotFoundException e ) {
			logger.debug( e );
			model.addAttribute( "status", "app.user.not.found" );
		}

		return "users/profile";
	}
	
	@RequestMapping( value = { "/profile/edit" }, method = RequestMethod.POST )
	@PreAuthorize("hasAnyRole('Administrator','Wykładowca','Student')")
	public String editProfile( final UserDTO userDTO, final ModelMap model ) {
		try {
			Users user = service.findById( userDTO.getId() );
			model.addAttribute( "user", UserMapper.map( user ) );
		} catch ( UserNotFoundException e ) {
			logger.debug( e );
			model.addAttribute( "status", "app.user.not.found" );
		}
		
		return "users/profileEdit";
	}
	
	@RequestMapping( value = { "/profile/update" }, method = RequestMethod.POST )
	@PreAuthorize("hasRole('Administrator')")
	public String updateUserData( final UserDTO dto, final ModelMap model ) {

		Users user = null; 
		try {
			user = service.findById( dto.getId() );
		} catch ( UserNotFoundException e ) {
			logger.debug( e );
			model.addAttribute( "status", "app.user.not.found" );
		}
		user.setFirstName( dto.getFirstName() );
		user.setLastName( dto.getLastName() );
		user.setLogin( dto.getUserName() );
		user.setStatus( dto.isStatus() );
		user.setRoles( roleService.findById( dto.getRoles().getId() ) );

		try {
			service.update( UserMapper.map( user ) );
		} catch ( UserNotFoundException e ) {
			logger.debug( e );
			model.addAttribute( "status", "app.user.not.found" );
		}

		model.addAttribute( "user", UserMapper.map( user ) );
		model.addAttribute( "status", "profile.edit.success" );

		return "users/profile";
	}
	
	@RequestMapping( value = { "/profile/password/update" }, method = RequestMethod.POST )
	public String updateUserPassword( final UserDTO dto, final ModelMap model ) throws UserNotFoundException {

		Users user = null;
		try {
			user = service.findById( dto.getId() );
			model.addAttribute( "user", UserMapper.map( user ) );
		} catch ( UserNotFoundException e ) {
			logger.debug( e );
			model.addAttribute( "status", "app.user.not.found" );
		}
		
		if ( StringUtils.isNotBlank( dto.getPassword() ) && StringUtils.isNotBlank( dto.getNewPassword() ) && StringUtils.isNotBlank( dto.getRePassword() ) ) {

			final String hashedOldPassword = DigestUtils.md5DigestAsHex( dto.getPassword().getBytes() );

			String pass = service.findById( dto.getId() ).getPassword();
			if ( !hashedOldPassword.equalsIgnoreCase( pass ) ) {
				model.addAttribute( "status", "profile.password.edit.incorrect" );
				return "users/profile";
			}

			if ( !dto.getNewPassword().equalsIgnoreCase( dto.getRePassword() ) ) {
				model.addAttribute( "status", "profile.password.edit.notmatch" );
				return "users/profile";
			}

			final String hashedNewPassword = DigestUtils.md5DigestAsHex( dto.getNewPassword().getBytes() );
			user.setPassword( hashedNewPassword );
			try {
				service.update( UserMapper.map( user ) );
			} catch ( UserNotFoundException e ) {
				logger.debug( e );
				model.addAttribute( "status", "app.user.not.found" );
			}
			model.addAttribute( "status", "profile.password.edit.success" );
		}else{
			model.addAttribute( "status", "profile.password.edit.failure" );
		}

		return "users/profile";
	}

	@RequestMapping( value = { "/students/group/" }, method = RequestMethod.POST )
	public String getStudentsInGroup( final GroupDTO groupDTO, final ModelMap model, @RequestParam( required = false ) final String order, @RequestParam( required = false ) final String column ) {
		Page< Users > students = getStudents(groupDTO.getId(), new PageRequest( DEFAULT_PAGE-1, STUDENTS_PERPAGE, Utils.getSortingDirection( order ), getSortedColumn( column ) ));
			
		model.addAttribute( "users", UserMapper.map( students ));
		model.addAttribute( "groupDto", groupDTO );
		model.addAttribute( "order", "ASC".equals( order ) ? "DESC": "ASC");
		model.addAttribute( "column", getSortedColumn( column ) );
		idGroup = groupDTO.getId();

		return "users/studentsList";
	}
	@RequestMapping( value = { "/students/group/" }, method = RequestMethod.GET )
	public String getStudentsInGroupSorted( final GroupDTO groupDTO, final ModelMap model, @RequestParam( required = false ) final String order, @RequestParam( required = false ) final String column ) {
		Page< Users > students = getStudents(idGroup, new PageRequest( DEFAULT_PAGE-1, USERS_PER_PAGE, Utils.getSortingDirection( order ), getSortedColumn( column ) ));
		
		groupDTO.setId( idGroup );
		model.addAttribute( "users", UserMapper.map( students ));
		model.addAttribute( "groupDto", groupDTO );
		model.addAttribute( "order", "ASC".equals( order ) ? "DESC": "ASC");
		model.addAttribute( "column", getSortedColumn( column ) );
		
		return "users/studentsList";
	}

	@RequestMapping( { "/teachers/subjects/{id}" } )
	public String getSubjectsForTeacher( @PathVariable( value = "id" ) final Integer id, final ModelMap model ) {

		try {
			Users user = service.findById( id );
			model.addAttribute( "user", UserMapper.map( user ) );
		} catch ( UserNotFoundException e ) {
			logger.debug( e );
			model.addAttribute( "status", "teachers.not.found" );
			getPage( "teachers", DEFAULT_PAGE, model, Utils.getSortingDirection( StringUtils.EMPTY ).toString(), StringUtils.EMPTY, StringUtils.EMPTY );
			return "users/teachersList";
		}

		return "users/subjects";
	}

	@RequestMapping( { "/{userType}" } )
	@PreAuthorize("hasRole('Administrator') OR hasRole('Wykładowca') OR (hasRole('Student') AND #userType == 'teachers')")
	public String defaultPage( @PathVariable final String userType, final ModelMap model, @RequestParam( required = false ) final String order, @RequestParam( required = false ) final String column, @RequestParam( required = false ) final String searchString ) {
		getPageWithNumber( userType, DEFAULT_PAGE, model, order, column, searchString ); 
		return "users/" + userType + "List";
	}

	@RequestMapping( "/{userType}/{pageNumber}" )
	@PreAuthorize("hasRole('Administrator') OR hasRole('Wykładowca') OR (hasRole('Student') AND #userType == 'teachers')")
	public String getPageWithNumber( @PathVariable final String userType, @PathVariable final Integer pageNumber, final ModelMap model, @RequestParam( required = false ) final String order, @RequestParam( required = false ) final String column,
			@RequestParam( required = false ) final String searchString ) {
		getPage( userType, pageNumber, model, order, column, searchString );
		return "users/" + userType + "List";
	}

	private void getPage( final String userType, final Integer pageNumber, final ModelMap model, final String order, final String column, final String searchString ) {
		final Page< Users > usersPage = getUsersPerPage( userType, searchString, new PageRequest( pageNumber - 1, USERS_PER_PAGE, Utils.getSortingDirection( order ), getSortedColumn( column ) ) );
		final int currentPage = usersPage.getNumber() + 1;
		final int beginPage = Math.max( 1, currentPage - 5 );
		final int endPage = Math.min( beginPage + 10, ( usersPage.getTotalPages() == 0 ? 1 : usersPage.getTotalPages() ) );
		final int totalPages = ( usersPage.getTotalPages() == 0 ? 1 : usersPage.getTotalPages() );

		model.addAttribute( "beginIndex", beginPage );
		model.addAttribute( "endIndex", endPage );
		model.addAttribute( "currentIndex", currentPage );
		model.addAttribute( "totalPages", totalPages );
		model.addAttribute( "users", UserMapper.map( usersPage ) );
		model.addAttribute( "order","ASC".equals( order ) ? "DESC": "ASC");
		model.addAttribute( "column", getSortedColumn( column ) );
		model.addAttribute( "searchHelper", new SearchDTO( searchString ) );
		model.addAttribute( "groupDto", new GroupDTO() );
	}

	@RequestMapping( value = { "/{userType}/create" }, method = RequestMethod.GET )
	@PreAuthorize("hasRole('Administrator') OR (hasRole('Wykładowca') AND #userType == 'students')")
	public String create( @PathVariable final String userType, final ModelMap model ) {
		model.addAttribute( "userDTO", new UserDTO() );
		model.addAttribute( "type", "create" );
		model.addAttribute( "userType", userType );
		return "users/" + userType + "Edit";
	}

	@RequestMapping( value = { "/{userType}/create" }, method = RequestMethod.POST )
	@PreAuthorize("hasRole('Administrator') OR (hasRole('Wykładowca') AND #userType == 'students')")
	public String create( @PathVariable final String userType, @Valid final UserDTO userDTO, BindingResult bindingResult, final ModelMap model ) {

		if ( bindingResult.hasErrors() ) {
			model.addAttribute( "type", "create" );
			model.addAttribute( "userType", userType );
			return "users/" + userType + "Edit";
		}
		
		try {
			Users userFound = service.findByLogin( userDTO.getUserName() );

			if ( userFound != null ) {
				throw new UserNameAlreadyExistsException();
			}

		} catch ( UserNameAlreadyExistsException ex ) {
			model.addAttribute( "status", "app.user.exists" );
			model.addAttribute( "userDTO", userDTO );
			model.addAttribute( "type", "create" );
			logger.debug( ex );

			return "users/" + userType + "Edit";
		} catch ( UserNotFoundException e ) {
		}

		try {
			if ( userDTO.getRoles() == null ) {
				final Roles role = roleService.findByRoleName( TEACHERS.equals( userType ) ? TEACHER_ROLE_NAME : STUDENT_ROLE_NAME );
				userDTO.setRoles( role );
			}
			service.create( userDTO );
			model.addAttribute( "status", userType + ".create.success" );
		} catch ( final Exception ex ) {
			model.addAttribute( "status", userType + ".create.failure" );
			logger.debug( ex );
		}

		getPage( userType, DEFAULT_PAGE, model, Utils.getSortingDirection( StringUtils.EMPTY ).toString(), StringUtils.EMPTY, StringUtils.EMPTY );

		return "users/" + userType + "List";
	}

	@RequestMapping( value = { "/{userType}/edit/{id}" }, method = RequestMethod.GET )
	@PreAuthorize("hasRole('Administrator') OR (hasRole('Wykładowca') AND #userType == 'students')")
	public String edit( @PathVariable final String userType, @PathVariable( value = "id" ) final Integer id, final ModelMap model ) {

		if ( id != null ) {
			try {
				Users user = service.findById( id );
				final UserDTO userDto = user == null ? new UserDTO() : UserMapper.map( user );

				model.addAttribute( "userDTO", userDto );
				model.addAttribute( "type", "edit" );
				model.addAttribute( "userType", userType );
			} catch ( UserNotFoundException e ) {
				logger.debug( e );
				model.addAttribute( "status", "app.user.not.found" );
				getPage( userType, DEFAULT_PAGE, model, Utils.getSortingDirection( StringUtils.EMPTY ).toString(), StringUtils.EMPTY, StringUtils.EMPTY );

				return "users/" + userType + "List";
			}
		}

		return "users/" + userType + "Edit";
	}

	@RequestMapping( value = { "/{userType}/edit" }, method = RequestMethod.POST )
	@PreAuthorize("hasRole('Administrator') OR (hasRole('Wykładowca') AND #userType == 'students')")
	public String update( @PathVariable final String userType, @Valid final UserDTO userDTO, BindingResult bindingResult, final ModelMap model ) {
		
		if ( bindingResult.hasErrors() ) {
			model.addAttribute( "type", "edit" );
			model.addAttribute( "userType", userType );
			return "users/" + userType + "Edit";
		}
		
		try {
			service.update( userDTO );
			model.addAttribute( "status", userType + ".update.success" );
		} catch ( final Exception ex ) {
			model.addAttribute( "status", userType + ".update.failure" );
			logger.debug( ex );
		}
		getPage( userType, DEFAULT_PAGE, model, Utils.getSortingDirection( StringUtils.EMPTY ).toString(), StringUtils.EMPTY, StringUtils.EMPTY );

		return "users/" + userType + "List";
	}

	@RequestMapping( value = "/{userType}/delete/{id}" )
	@PreAuthorize("hasRole('Administrator') OR (hasRole('Wykładowca') AND #userType == 'students')")
	public String delete( @PathVariable final String userType, @PathVariable( value = "id" ) final Integer id, final ModelMap model ) {
		try {
			service.delete( id );
			model.addAttribute( "status", userType + ".delete.success" );
		} catch ( final UserNotFoundException ex ) {
			model.addAttribute( "status", userType + ".delete.failure" );
			logger.debug( ex );
		} catch ( final EntityConstraintException ex ) {
			model.addAttribute( "status", userType + ".constraint.exception" );
			logger.debug( ex );
		}

		getPage( userType, DEFAULT_PAGE, model, Utils.getSortingDirection( StringUtils.EMPTY ).toString(), StringUtils.EMPTY, StringUtils.EMPTY );
		return "users/" + userType + "List";
	}

	private Page< Users > getUsersPerPage( final String userType, final String searchString, final PageRequest pageRequest ) {
		return TEACHERS.equals( userType ) ? getTeachers( searchString, pageRequest ) : new PageImpl< Users >( new ArrayList< Users >() );
	}

	private Page< Users > getStudents( final Integer id, final PageRequest pageRequest ) {
		return service.findAllStudentsInGroup( id, pageRequest );
	}

	private Page< Users > getTeachers( final String searchString, final PageRequest pageRequest ) {
		if ( StringUtils.isEmpty( searchString ) ) {
			return service.findAllTeachers( pageRequest );
		} else {
			return service.findAllTeachersMatching( searchString, pageRequest );
		}
	}

	private String getSortedColumn( String column ) {
		if ( StringUtils.isEmpty( column ) ) {
			column = FIRST_NAME;
		} else if ( StringUtils.isNotEmpty( column )
				&& ( !column.equals( FIRST_NAME ) && !column.equals( LAST_NAME ) && !column.equals( STATUS ) && !column.equals( USER_NAME ) && !column.equals( EMAIL ) && !column.equals( GROUP_NAME ) && !column.equals( ALBUM_NUMBER ) ) ) {
			column = FIRST_NAME;
		}
		return column;
	}
}